package plp.functional2.primitive;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import plp.expressions2.expression.Expressao;
import plp.expressions2.expression.Id;
import plp.expressions2.expression.Valor;
import plp.expressions2.memory.AmbienteCompilacao;
import plp.expressions2.memory.AmbienteExecucao;
import plp.expressions2.memory.ContextoExecucao;
import plp.expressions2.memory.VariavelJaDeclaradaException;
import plp.expressions2.memory.VariavelNaoDeclaradaException;
import plp.functional2.concurrency.MailBox;


public class Receive implements Primitiva {

	private List<Clausula> clausulas;
	private Id idProcess;

	public Receive(List<Clausula> clausulas) {
		this.clausulas = clausulas;System.out.println("### Receive() ###");
	}

	public boolean checaTipo(AmbienteCompilacao ambiente)throws VariavelNaoDeclaradaException, VariavelJaDeclaradaException {
		Clausula clausula;
		boolean isChecked = false;

		for (int i = 0; i < clausulas.size(); i++) {
			clausula = clausulas.get(i);
			isChecked = clausula.checaTipo(ambiente);
			if (!isChecked){
				break;
			}
		}

		return isChecked;
	}

	public void executar(AmbienteExecucao ambiente)	throws VariavelNaoDeclaradaException, VariavelJaDeclaradaException {
		ContextoExecucao contexto = (ContextoExecucao) ambiente;
		MailBox mailBox = MailBox.getInstance();
		
		Stack <HashMap<Id,Valor>> pilha =  (Stack <HashMap<Id,Valor>>)contexto.getPilha().clone();
		Message mensagem = null;
				
		if(idProcess == null)
			throw new VariavelNaoDeclaradaException(idProcess);

		Iterator<Clausula> itClausulas = clausulas.iterator();
		while (itClausulas.hasNext()) {// Verifica se há padrão e executo o que casar
			Clausula clausula = itClausulas.next();

			if ((mensagem = mailBox.getMessage(idProcess, clausula.getID())) != null) {System.out.println("### Receive:Received ###");

				List<Id> cPattern = clausula.getID();
				List<Expressao> msgPattern = mensagem.getPattern();

				Iterator<Id> itPattern = cPattern.iterator();
				Iterator itMsg = msgPattern.iterator();

				//Removing the pattern initial
				itPattern.next();
				itMsg.next();

				Map<Id,Valor> idValor = new HashMap<Id,Valor>();

				//Atribui um valorReal para cada Id - Biding
				while(itPattern.hasNext()){
					Expressao valorExp = (Expressao) itMsg.next();
					Expressao idExp = itPattern.next();
					
					if (!(valorExp instanceof Valor)) {
						continue;
					}
					
					Valor valor = (Valor) valorExp;
					Id id = (Id) idExp;
					idValor.put(id, valor);							
				}

				contexto.incrementa();
				includeValueBindings(contexto, idValor);						

				Primitiva primitiva = clausula.getPrimitivas();
				primitiva.executar(ambiente);
				contexto.restaura();
				break;
			}		
		}

		return;		
	}

	private void includeValueBindings(AmbienteExecucao ambiente,Map<Id, Valor> mapIdValor) throws VariavelJaDeclaradaException {
		for (Map.Entry<Id, Valor> mapeamento : mapIdValor.entrySet()) {
			Id id = (Id) mapeamento.getKey();
			Valor valor = (Valor) mapeamento.getValue();
			ambiente.map(id, valor);
		}
	}

	public Id getIdProcess() {
		return idProcess;
	}

	public void setIdProcess(Id idProcess) {
		this.idProcess = idProcess;
	}
}